

//select直接从接受连接开始
//select需要定义文件描述符集
//添加套接字到文件描述集中
//设置时间规定

#pragma

#include<iostream>
#include<functional>
#include<string>
#include"sock.hpp"
#include"log.hpp"
#include"err.hpp"
namespace select_ns
{  static const int16_t nport=8080;
   static const int fdnum=sizeof(fd_set)*8;
   static const int defaultfd=-1;
  using func_t=std::function<std::string(std::string)>;
  class SelectServer
  {
public: 
   SelectServer(func_t func,int16_t port=nport):_port(port),_listensock(-1),fdarray(nullptr),_func(func){}//对成员变量初始化
   void initServer()//创建监听套接字进行监听有无连接请求，然后对数组进行开空间初始化
   { 
    _listensock=Sock::Socket();
    Sock::Bind(_listensock,_port);
    Sock::Listen(_listensock);
    fdarray=new int[fdnum];
    fdarray[0]=_listensock;
    for(int i=1;i<fdnum;i++)
    {
      fdarray[i]=-1;//没有套接字就初始化为-1
    }

   }
   void Print()//对当前数组中的文件描述符做打印
     { std::cout<<"fd list:";
     for(int i=0;i<fdnum;i++)
     { std::cout<<"fd list:";
      if(fdarray[i]==defaultfd) continue;
      std::cout<<fdarray[i];
     }
     std::cout<<std::endl;
     
   }
   void Accepter(int listensock)//单独对监听套接字做处理，创建新的读写文件描述符，然后放入到数组中。
   {   logMessage(DEBUG, "Accepter in");
        std::string clientip;
        std::uint16_t clientport;
       int sock=Sock::Accept(listensock,&clientip,&clientport);
       if(sock<0)
       {
          return;
       }
        logMessage(NORMAL, "accept success [%s:%d]", clientip.c_str(), clientport);
       int i=0;
       for(i=0;i<fdnum;i++)
       {
        if(fdarray[i]!=defaultfd)continue;
        else
        break;
       }
       if(i==fdnum)
       {
         logMessage(WARNING, "server if full, please wait");
                close(sock);
       }
       else
       {fdarray[i]=sock;}
       Print();
       logMessage(DEBUG, "Accepter out");
   }
   void Recver(int sock,int pos)//对读写文件描述符做处理，接收客户端发来的信息，对信息做处理，处理之后在回复给客户端
   { logMessage(DEBUG, "in Recver");
    for(;;)
   {char buffer[2048];
      size_t n=recv(sock,buffer,sizeof(buffer)-1,0);
      if(n>0)
      {
        buffer[n]=0;
         logMessage(NORMAL, "client# %s", buffer);
      }
      else if(n==0)
      { 
        close(sock);
        fdarray[pos]=defaultfd;
      logMessage(NORMAL, "client quit");
                return;

      }
      else 
      { close(sock);
       fdarray[pos] = defaultfd;
                logMessage(ERROR, "client quit: %s", strerror(errno));
                return;
      }
      std::string response=_func(buffer);
      write(sock,response.c_str(),response.size());
      logMessage(DEBUG, "out Recver");
 
     
    }




   }
   void HandlerEvent(fd_set fds)//处理对应描述符的事件，如果是监听套接字就对监听套接字做处理，如果是读写套接字就对读写套接字做处理
   {  
     for(int i=0;i<fdnum;i++)
     {
      if(fdarray[i]==defaultfd)
      continue;
      if(FD_ISSET(fdarray[i],&fds)&&fdarray[i]==_listensock)
      {
        Accepter(_listensock);
      }
      else if(FD_ISSET(fdarray[i],&fds))
      {
        Recver(fdarray[i],i);
      }
      else 
      {
  
      }
     }



   }
   void start()//设置读的结构位图，将位图清0，填加新的描述符到集合中，然后select接管这些描述符，根据返回值来决定是否收到描述符发来的事件，
   {
      for(;;)
      {
        fd_set rfds;//select需要监听的集合
        FD_ZERO(&rfds);
        int maxfd=fdarray[0];//设置最初的位图
        for(int i=0;i<fdnum;i++)
        {
          if(fdarray[i]==defaultfd)
          {
            continue;
          }
          FD_SET(fdarray[i],&rfds);//把数组中存在的套接字存放进位图
          if(maxfd<fdarray[i])
          {
            maxfd=fdarray[i];//更新所有fd中最大的fd
          }
        }
         logMessage(NORMAL, "max fd is: %d", maxfd);
         int n=select(maxfd+1,&rfds,nullptr,nullptr,nullptr);//阻塞等待
         switch(n)
         { 
          case 0:
           logMessage(NORMAL,"timeout...");
           break;
           case -1:
             logMessage(WARNING,"select err,code:%d,err string:%s",errno,strerror(errno));
           break;
           default:
           logMessage(NORMAL,"have event");
           HandlerEvent(rfds);
           break;

         }

      }
   }


  ~SelectServer(){}//关闭套接字和释放数组内存
  private:
   int16_t _port;//端口号
   int _listensock;//监听套接字
   int *fdarray;//存放对应客户端的文件描述符
   func_t _func;//对客户端的信息做回调 

  };




}
